# -*- coding: utf-8 -*-
"""
Created on Mon Dec  1 15:05:49 2014

@author: wei
"""

from app import db,cache
from sqlalchemy import types
from flask_login import current_user
from sqlalchemy_utils import PasswordType,ChoiceType
from sqlalchemy_utils.types import choice
import os
from datetime import datetime,date
from utils import strcrypt
from decimal import Decimal

class AttrDict(dict):
    def __init__(self, **kwargs):
        super(AttrDict, self).__init__(**kwargs)
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value
    def __hash__(self):
        return self.id


class SerializableMixin(object):
    def as_dict(self,*fields,**field_formatter):
        result = AttrDict()
        fns=fields or self.__mapper__.c.keys()
        for fn in fns:
            field_value=None

            if ':' in fn:
                fieldname,asname=fn.split(':')
            else:
                fieldname=asname=fn

            if hasattr(self,fieldname):
                field_value=getattr(self, fieldname)
            else:
                obj=self
                for k in fieldname.split('.'):
                    if not hasattr(obj,k):
                        raise Exception('字段[%s]不存在!' % fieldname)
                    field_value=getattr(obj,k)
                    if field_value:
                        obj=field_value
                    else:
                        field_value=''
                        break

            if asname in field_formatter and field_value:
                field_value=field_formatter[asname](field_value)

            if  isinstance(field_value,bool):
                value=field_value and 'T' or 'F'
            elif field_value is None or isinstance(field_value,(str,int,float,
                                                                dict,list,tuple)):
                value=field_value
            elif isinstance(field_value,datetime):
                value=field_value.strftime('%Y-%m-%d %H:%M:%S')
            elif isinstance(field_value,date):
                value=field_value.strftime('%Y-%m-%d')
            elif isinstance(field_value,Decimal):
                scale=abs(field_value.as_tuple().exponent) or 2
                fmt='%%0.%df' % scale
                value=fmt % field_value
            elif isinstance(field_value,choice.Choice):
                result['%s_name' % asname]=field_value.value
                value=field_value.code
            else:
                raise Exception('字段[%s]:不支持系列化!(type:[%s])' % (field_value,type(field_value)))
            result[asname] = value

        return result

    def as_json(self,*fields,**field_formatter):
        d=self.as_dict(*fields,**field_formatter)
        return json.dumps(d)

class StringEncryptType(types.TypeDecorator):
    impl = types.String
    def __init__(self,*args,**kwargs):
        self.secret_key=kwargs.pop('secret_key','abc123')
        super().__init__(*args,**kwargs)
        
    def process_bind_param(self, value, dialect):
        if self.secret_key:
            return strcrypt.en_str(self.secret_key,value)
        else:
            return value

    def process_result_value(self, value, dialect):
        if self.secret_key:
            return strcrypt.de_str(self.secret_key,value)
        else:
            return value

    def copy(self):
        return StringEncryptType(self.impl.length,secret_key=self.secret_key)
        
class Users(db.Model,SerializableMixin):
    STATUS=[('STA_ACTIVE','正常'),('STA_DISACTIVE','已禁用')]
    id          =db.Column(db.Integer, primary_key = True)#nullable=False, unique=True, autoincrement=True
    username    =db.Column(db.String(64), index = True, unique = True)
    remarkname  =db.Column(db.String(64))
    pwd         =db.Column(PasswordType(schemes=['pbkdf2_sha512','md5_crypt'],
                                        deprecated=['md5_crypt']))
    status      =db.Column(ChoiceType(STATUS),default='STA_ACTIVE',nullable=False)
    is_admin    =db.Column(db.Boolean,default=False,nullable=False)
    email       =db.Column(db.String(120), index = True, unique = True)
    created_user_id =db.Column(db.Integer, db.ForeignKey('users.id'),
                               default=lambda: current_user.id if current_user else None)
    created_date    =db.Column(db.DateTime, default = db.func.now())
    last_seen       =db.Column(db.DateTime)

    creator =db.relationship('Users', remote_side=id, backref=db.backref('created_users',lazy='dynamic'))

    def is_authenticated(self):
        return True

    def is_active(self):
        return self.status=="STA_ACTIVE"

    def is_anonymous(self):
        return False

    def get_id(self):
        return str(self.id)

    def __repr__(self):
        return '<User %r>' % (self.username)

    def set_pwd(self,old_pwd,new_pwd):
        if self.pwd:#检查旧密码
            if self.pwd!=old_pwd:
                return False

        self.pwd=new_pwd
        return True

    def check_pwd(self,pwd):
        return self.pwd==pwd

class Configs(db.Model,SerializableMixin):
    id       =db.Column(db.Integer, primary_key = True)
    key      =db.Column(db.String(64), index= True, unique = True)
    value    =db.Column(db.String(1000),default = '')
    descript =db.Column(db.String(200),default = '')
    num      =db.Column(db.Integer,default=0)

    def __repr__(self):
        return '<Config key=%r>' % self.key

    @classmethod
    def __create_cache_key(self,key):
        return 'config_cache_%s' % key

    @classmethod
    def get_value(self,key,default='',descript='',cache_timeout=60):
        cache_key=self.__create_cache_key(key)
        value=cache.get(cache_key)
        if value is None:
            value=self.query.filter_by(key=key).value('value')
            if value is None:
                value=default
                cfg=self(key=key,value=default,descript=descript)
                db.session.add(cfg)
                db.session.flush()
            if cache_timeout>0:
                cache.set(cache_key,value,timeout=cache_timeout)

        return value

    @classmethod
    def set_value(self,key,value,**kwargs):
        cfg=self.query.filter_by(key=key).first()
        if not cfg:
            cfg=self(key=key)
        cfg.value=value

        for k,v in kwargs.items():
            if hasattr(cfg,k):
                setattr(cfg,k,v)

        db.session.add(cfg)
        db.session.flush()

        #删除缓存
        cache_key=self.__create_cache_key(key)
        cache.delete(cache_key)

    @classmethod
    def del_value(self,key):
        self.query.filter_by(key=key).delete()
        db.session.flush()

    @classmethod
    def inc_num(self,key):
        update=db.update(self).values(num=self.num+1).where(self.key==key)
        db.session.execute(update)
        db.session.flush()
        num=self.query.filter_by(key=key).value('num')
        return num

    @classmethod
    def get_num(self,key):
        cfg=self.query.filter_by(key=key).first()
        if cfg is None:
            cfg=self(key=key,value='',descript='',num=1)
            db.session.add(cfg)
        else:
            cfg.num=self.num+1
            db.session.add(cfg)
        db.session.flush()
        return cfg.num


class Repository(db.Model,SerializableMixin):#仓库表
    id        =db.Column(db.Integer, primary_key = True)
    name      =db.Column(db.String(64), nullable=False,default = '')
    remark    =db.Column(db.String(200),default = '')
    
    def __repr__(self):
        return '<Repository %r>' % self.name
     
    @property
    def location(self):
        return os.path.join(Configs.get_value('SVN_REPOS_PATH'),self.name)
    
class Servers(db.Model,SerializableMixin):#服务器信息
    id      =db.Column(db.Integer, primary_key = True)
    host    =db.Column(db.String(64),nullable=False,default='')
    port    =db.Column(db.Integer,nullable=False,default=22)
    user    =db.Column(db.String(64),nullable=False,default='')
    pwd     =db.Column(StringEncryptType(100,secret_key='@2014*&'),nullable=False,default='')
    remark  =db.Column(db.String(200),default = '')
    
    def __repr__(self):
        return '<Servers %r>' % self.host
        
class Deploy_info(db.Model,SerializableMixin):#布署配置信息
    id           =db.Column(db.Integer, primary_key = True)
    repository_id=db.Column(db.Integer, db.ForeignKey('repository.id'))
    server_ids   =db.Column(db.String(200),nullable=False,default='')
    branch       =db.Column(db.String(200),nullable=False,default='')
    deploy_path  =db.Column(db.String(200),nullable=False,default='')#发布目录
    init_cmds    =db.Column(db.String(500),nullable=False,default='')#初始化命令
    cmds         =db.Column(db.String(500),nullable=False,default='')#命令
    is_deploying =db.Column(db.Boolean,default=False,nullable=False)
    deploytime   =db.Column(db.DateTime)
    
    repository   =db.relationship("Repository",backref=db.backref('deploy_infos',lazy='dynamic'))

    def __repr__(self):
        return '<Deploy_info %r>' % self.branch
    
    @property
    def deploy_servers(self):
        return ','.join([host for host, in Servers.query.filter(\
                        Servers.id.in_(self.server_ids)).with_entities(Servers.host)])
                        
    @property
    def repository_name(self):
        repos=self.repository
        return repos.name if repos else ''
        
    @property 
    def deploy_status(self):
        stat=False
        if not self.is_deploying:
            stat=True
        else:
            if self.deploytime:
                tdelta=datetime.now()-self.deploytime
                if tdelta.days>0 or tdelta.seconds>1*60*60:#1小时
                    stat=True
       
        if stat:#可操作
            return '1'
        else:#发布中
            tdelta=datetime.now()-self.deploytime
            return '发布中,请稍等[已用时:%d分%.2d秒]...' % (tdelta.seconds//60,tdelta.seconds%60)
            
